#include "cache_controller.h"

void cache_controller::business(void)
{
  busy.write(SC_LOGIC_0);
  
  while (true) {

    wait();

    if (state.read() == 0b100) {
      busy.write(SC_LOGIC_1);
      wait(10, SC_NS);
      busy.write(SC_LOGIC_0);
    }
  }
}

void cache_controller::fsm(void)
{
  sc_lv<3>  __s = 0b000;

  while (true) {

    wait();

    switch (state.read().to_uint()) {

      /* IDLE */
      case 0b000:
        if (cs.event() && 
            cs.read() == SC_LOGIC_1)
          __s = 0b010;
        break;

      /* READ CACHE */
      case 0b010:
        // reset data input source
        _din_src = SC_LOGIC_0;
        _block = SC_LOGIC_0;

        if (cs.read() == SC_LOGIC_1) {

          if (c_rdy.event() && 
              c_rdy.read() == SC_LOGIC_1) {

            /* hit */
            if (c_hit.read() == SC_LOGIC_1 &&
                c_tout.read()[1] == SC_LOGIC_1) {

              // prepare data output according to addr offset
              // prepare write cache buffer, if CPU write issued
              switch (addr.read()(3, 0).to_uint()) {
                case 0x4:
                  _c_dout       = c_dout1.read();
                  _wc_buffer[1] = din.read();
                  break;

                case 0x8:
                  _c_dout       = c_dout2.read();
                  _wc_buffer[2] = din.read();
                  break;

                case 0xC:
                  _c_dout       = c_dout3.read();
                  _wc_buffer[3] = din.read();
                  break;

                case 0x0:
                default:
                  _c_dout       = c_dout0.read();
                  _wc_buffer[0] = din.read();
              } // hit

              
              /* write from CPU */
              if (rw.read() == SC_LOGIC_1) {
                _din_src = SC_LOGIC_1;
                _block = SC_LOGIC_1;
                __s = 0b111;

              /* read */
              } else if (rw.read() == SC_LOGIC_0)
                __s = 0b100;

            /* compulsory miss */
            } else if (c_hit.read() == SC_LOGIC_0 &&
                       c_tout.read()[1] == SC_LOGIC_0)
              __s = 0b110;

            /* miss */
            else if (c_hit.read() == SC_LOGIC_0 &&
                       c_tout.read()[1] == SC_LOGIC_1)
              __s = 0b011;
            
          }

        } // cs
        break;

      /* READ MEM */
      case 0b110:
        if (cs.read() == SC_LOGIC_1) {

          // fill write cache buffer
          _wc_buffer[0] = m_dout0.read();
          _wc_buffer[1] = m_dout1.read();
          _wc_buffer[2] = m_dout2.read();
          _wc_buffer[3] = m_dout3.read();

          if (m_rdy.event() &&
              m_rdy.read() == SC_LOGIC_1)
            __s = 0b111;

        } // cs
        break;

      /* WRITE CACHE */
      case 0b111:
        if (cs.read() == SC_LOGIC_1) {

          if (c_rdy.event() && 
              c_rdy.read() == SC_LOGIC_1) {

            if (rw.read() == SC_LOGIC_1 &&
                c_hit.read() == SC_LOGIC_1 &&
                c_tout.read()[1] == SC_LOGIC_1) // notify write success
              __s = 0b100;

            else
              __s = 0b010;
            
          }

        } // cs
        break;

      /* READ VICTIM CACHE */
      case 0b011:
        if (cs.read() == SC_LOGIC_1) {

          /* check for victim hit/miss/write back - 
           *
           * miss */
          if (v_hit.read() == SC_LOGIC_0)
            _v_hmw = 0b01;

          if (v_tout.read()[1] == SC_LOGIC_1) {
            /* write back */
            if (v_tout.read()[0] == SC_LOGIC_1)
              _v_hmw = 0b10;

            /* hit */
            if (v_hit.read() == SC_LOGIC_1)
              _v_hmw = 0b11;
          }

          // address written in victim cache must match
          // the cache block.
          _wv_addr.range(31, 9) = c_tout.read()(24, 2);
          _wv_addr.range( 8, 0) = addr.read()(8, 0);

          // fill write cache buffer
          _wc_buffer[0] = v_dout0.read();
          _wc_buffer[1] = v_dout1.read();
          _wc_buffer[2] = v_dout2.read();
          _wc_buffer[3] = v_dout3.read();

          // prepare write back address
          _wb_addr.range(31, 4) = v_tout.read()(29, 2);
          _wb_addr.range( 3, 0) = 0;

          if (v_rdy.event() &&
              v_rdy.read() == SC_LOGIC_1)
            __s = 0b001;

        } // cs
        break;

      /* WRITE VICTIM CACHE */
      case 0b001:
        if (cs.read() == SC_LOGIC_1) {

          // XXX: here or READ VICTIM CACHE
          //      better here because v_tout
          //      is refreshed?
          if (v_tout.read()[0] == SC_LOGIC_1)
            _din_src = SC_LOGIC_1;

          if (v_rdy.event() &&
              v_rdy.read() == SC_LOGIC_1) {

            // according to victim cache read response
            // act properly.
            switch (_v_hmw.to_uint()) {

              /* hit */
              case 0b11:
                __s = 0b111;
                break;

              /* write back */
              case 0b10:
                __s = 0b101;
                break;
              
              /* miss */
              case 0b01:
              default:
                __s = 0b110;
            }

          }

        } // cs
        break;

      /* WRITE BACK */
      case 0b101:
        if (cs.read() == SC_LOGIC_1) {

          // after write back is performed
          // there's no need to maintain
          // _din_src HIGH.
          _din_src = SC_LOGIC_0;

          if (m_rdy.event() &&
              m_rdy.read() == SC_LOGIC_1)
            __s = 0b110;

        } // cs
        break;

      /* TO CPU */
      case 0b100:
        if (busy.event() &&
            busy.read() == SC_LOGIC_0)
          __s = 0b000;
        break;

      default:
        __s = 0b000;
    }

    state.write(__s);

  }
}

void cache_controller::fsm_logic(void)
{
  switch (state.read().to_uint()) {

    /* IDLE */
    case 0b000:
      rdy.write(SC_LOGIC_1);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* READ CACHE */
    case 0b010:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_1);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(addr.read());
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* READ MEM */
    case 0b110:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_1);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(addr.read());
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* WRITE CACHE */
    case 0b111:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_1);
      c_rw.write(SC_LOGIC_1);
      c_addr.write(addr.read());
      c_din0.write(_wc_buffer[0]);
      c_din1.write(_wc_buffer[1]);
      c_din2.write(_wc_buffer[2]);
      c_din3.write(_wc_buffer[3]);
      c_set_v.write(SC_LOGIC_1);
      c_din_src.write(_din_src);
      c_block.write(_block);
      break;

    /* TO CPU */
    case 0b100:
      rdy.write(SC_LOGIC_1);
      dout.write(_c_dout);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* READ VICTIM CACHE */
    case 0b011:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_1);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(addr.read());
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* WRITE VICTIM CACHE */
    case 0b001:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_1);
      v_rw.write(SC_LOGIC_1);
      v_addr.write(_wv_addr);
      v_din0.write(c_dout0.read());
      v_din1.write(c_dout1.read());
      v_din2.write(c_dout2.read());
      v_din3.write(c_dout3.read());
      v_set_v.write(SC_LOGIC_1);
      v_set_d.write(c_tout.read()[0]);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    /* WRITE BACK */
    case 0b101:
      rdy.write(SC_LOGIC_0);
      dout.write(0);
      m_cs.write(SC_LOGIC_1);
      m_rw.write(SC_LOGIC_1);
      m_addr.write(_wb_addr);
      m_din0.write(v_dout0.read());
      m_din1.write(v_dout1.read());
      m_din2.write(v_dout2.read());
      m_din3.write(v_dout3.read());
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
      break;

    default:
      rdy.write(SC_LOGIC_1);
      dout.write(0);
      m_cs.write(SC_LOGIC_0);
      m_rw.write(SC_LOGIC_0);
      m_addr.write(0);
      m_din0.write(0);
      m_din1.write(0);
      m_din2.write(0);
      m_din3.write(0);
      v_cs.write(SC_LOGIC_0);
      v_rw.write(SC_LOGIC_0);
      v_addr.write(0);
      v_din0.write(0);
      v_din1.write(0);
      v_din2.write(0);
      v_din3.write(0);
      v_set_v.write(SC_LOGIC_0);
      v_set_d.write(SC_LOGIC_0);
      c_cs.write(SC_LOGIC_0);
      c_rw.write(SC_LOGIC_0);
      c_addr.write(0);
      c_din0.write(0);
      c_din1.write(0);
      c_din2.write(0);
      c_din3.write(0);
      c_set_v.write(SC_LOGIC_0);
      c_din_src.write(SC_LOGIC_0);
      c_block.write(SC_LOGIC_0);
  }
}
